﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Threading;

namespace Fractals
{
    class Simulator
    {
        private static Random m_randomNumberGen = new Random();
        private Bitmap m_bitmap;
        private InterpolatedPictureBox m_pictureBox;
        private Form1 m_form1;
        
        public Color TargetColor {get; set;}
        public Color StartColor { get; set; }
        public Color ClearColor { get; set; }
        public bool Transparency { get; set; }
        public int ParticleCount { get; set; }
        public int ParticleMaxLifetime { get; set; }
        public string StartRegionType { get; set; }
        public string TargetRegionType { get; set; }
        public bool NeighborhoodMode { get; set; }
        public bool VerboseMode { get; set; }
        public bool LiveUpdateMode { get; set; }
        public bool ShowStartRegion { get; set; }
        public bool ShowTargetRegion { get; set; }
        public Image Image
        {
            get { return (Image)m_bitmap; }
            set
            {
                m_bitmap = (Bitmap) value;
            }
        }

        public Simulator(InterpolatedPictureBox picBox, int simWidth, int simHeight, Form1 form)
        {
            m_form1 = form;
            m_pictureBox = picBox;
            m_bitmap = new Bitmap(simWidth, simWidth, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            
            // initialize Properties
            TargetColor = Color.DarkRed;
            StartColor = Color.DarkGreen;
            ClearColor = Color.White;
            Transparency = true;
            ParticleCount = 4000;
            ParticleMaxLifetime = 9000;
            StartRegionType = "Everywhere";
            TargetRegionType = "Point";
            NeighborhoodMode = false;
            VerboseMode = false;
            LiveUpdateMode = false;
            ShowStartRegion = false;
            ShowTargetRegion = false;
        }

        /// <summary>
        /// runs the simulation
        /// </summary>
        /// <param name="bitmap">reference to bitmap where the simulation reads and writes to</param>
        public void simulate()
        {
            reSeed();

            // Create new stopwatch
            var stopwatch = System.Diagnostics.Stopwatch.StartNew();

            // clear bitmap
            Graphics myGraphics = Graphics.FromImage(m_bitmap);
            if (Transparency) ClearColor = Color.FromArgb(0 ,ClearColor.R, ClearColor.G, ClearColor.B);
            myGraphics.Clear(ClearColor);
            myGraphics.Dispose();

            m_pictureBox.Image = m_bitmap;
            m_pictureBox.Refresh();

            // create a new grid
            var grid = new Grid(m_bitmap.Width, m_bitmap.Height);

            // create the set startRegion and targetRegion
            List<Particle> startRegion = createStartRegion(m_bitmap);
            List<Particle> targetRegion = createTargetRegion(m_bitmap, grid);

            if (ShowStartRegion)
                drawRegion(startRegion, StartColor);
            if (ShowTargetRegion)
                drawRegion(targetRegion, TargetColor);

            int direction;
            bool hit;

            // for every particle
            for (int p = 0; p < ParticleCount; p++)
            {
                m_form1.ProgressBar = ((int)((double)(p + 1) / (double)(ParticleCount) * 100.0));
                
                if (VerboseMode)
                {
                    m_form1.ToolStripStatus = ("Processing particle: " + (p + 1) + " of " + ParticleCount);
                }
                
                // select a random startPosition out of the Set StartRegion
                int index = m_randomNumberGen.Next(startRegion.Count);
                var particle = startRegion[index];

                hit = false;

                // walk randomly until you hit an existing particle
                for (int i = 0; i < ParticleMaxLifetime; i++)
                {
                    // choose a random direction
                    direction = m_randomNumberGen.Next(4);
                    switch (direction)
                    {
                        case 0:
                            //North
                            if (particle.y + 1 < m_bitmap.Height)
                                particle.y += 1;
                            break;
                        case 1:
                            //East
                            if (particle.x + 1 < m_bitmap.Width)
                                particle.x += 1;
                            break;
                        case 2:
                            //South
                            if (particle.y - 1 >= 0)
                                particle.y -= 1;
                            break;
                        case 3:
                            //West
                            if (particle.x - 1 >= 0)
                                particle.x -= 1;
                            break;
                    }

                    if (checkNeighbors(grid, particle, NeighborhoodMode)) hit = true;

                    if (hit)
                    {
                        particle.lifetime = i;
                        grid.SetPixel(particle.x, particle.y);

                        // COLORING
                        var newColor = new HSLColor(StartColor);

                        double targetHue = new HSLColor(TargetColor).Hue;
                        double startHue = new HSLColor(StartColor).Hue;
                        double hueDiff = Math.Abs(startHue - targetHue);

                        // 0 means first particle
                        // 1 last particle
                        var lifetimeImpact = (float)p / (float)ParticleCount;

                        newColor.Hue = ((newColor.Hue + lifetimeImpact * hueDiff) % 360.0);
                        m_bitmap.SetPixel(particle.x, particle.y, newColor);

                        if (LiveUpdateMode)
                        {
                            // display unfinished bitmap
                            m_pictureBox.Refresh();
                        }
                        break;
                    }
                }
            }

            stopwatch.Stop();
            m_form1.ProgressBar = 0;
            Console.WriteLine("Simulation took: {0}", stopwatch.Elapsed);
        }

        private List<Particle> createTargetRegion(Bitmap bitmap, Grid grid)
        {
            List<Particle> targetRegion = new List<Particle>();
            if (TargetRegionType == "CenterPoint")
            {
                targetRegion.Add(new Particle((bitmap.Width / 2), (bitmap.Height / 2)));
                grid.SetPixel((bitmap.Width / 2), (bitmap.Height / 2));
            }
            if (TargetRegionType == "Line")
            {
                for (int x = 0; x < bitmap.Width; x++)
                {
                    targetRegion.Add(new Particle(x, bitmap.Height - 1));
                    grid.SetPixel(x, bitmap.Height - 1);
                }
            }
            if (TargetRegionType == "Circle")
            {
                var radius = Math.Min(bitmap.Width / 2, bitmap.Height / 2);
                var x0 = bitmap.Width / 2;
                var y0 = bitmap.Height / 2;

                targetRegion = rasterCircle(x0, y0, radius-1, bitmap, TargetColor);
                
                foreach (Particle p in targetRegion)
                    {
                        grid.SetPixel(p.x, p.y);
                    }
                }
            return targetRegion;
        }

        private List<Particle> createStartRegion(Bitmap bitmap)
        {
            List<Particle> startRegion = new List<Particle>();
            if (StartRegionType == "Everywhere")
            {
                for (int x = 0; x < bitmap.Width; x++)
                {
                    for (int y = 0; y < bitmap.Height; y++)
                    {
                        startRegion.Add(new Particle(x, y));
                    }
                }
            }
            if (StartRegionType == "Rectangle")
            {
                for (int x = 0; x < bitmap.Width; x++)
                {
                    startRegion.Add(new Particle(x, 0)); //top row
                    startRegion.Add(new Particle(x, bitmap.Height - 1)); //bottom row
                }
                for (int y = 0; y < bitmap.Height; y++)
                {
                    startRegion.Add(new Particle(0, y)); //left
                    startRegion.Add(new Particle(bitmap.Width - 1, y)); //right
                }
            }
            if (StartRegionType == "Circle")
            {
                var radius = Math.Min(bitmap.Width / 2, bitmap.Height / 2);
                var x0 = bitmap.Width / 2;
                var y0 = bitmap.Height / 2;

                startRegion = rasterCircle(x0, y0, radius - 1, bitmap, StartColor);
            }
            if (StartRegionType == "Line")
            {
                for (int x = 0; x < bitmap.Width; x++)
                {
                    startRegion.Add(new Particle(x, 0));
                }
            }
            if (StartRegionType == "CenterPoint")
            {
                startRegion.Add(new Particle((bitmap.Width / 2), (bitmap.Height / 2)));
            }
            return startRegion;
        }

        private void drawRegion(List<Particle> region, Color color)
        {
            foreach (Particle p in region)
            {
                m_bitmap.SetPixel(p.x, p.y, color);
            }
        }

        private bool checkNeighbors(Grid grid, Particle particle, bool moorMode)
        {
            if (moorMode)
            {
                // also check diagonal pixels
                if (particle.x + 1 < grid.Width && particle.y + 1 < grid.Height)
                {
                    if (grid.GetPixel(particle.x + 1, particle.y + 1) == true)
                        return true;
                }

                if (particle.x + 1 < grid.Width && particle.y - 1 >= 0)
                {
                    if (grid.GetPixel(particle.x + 1, particle.y - 1) == true)
                        return true;
                }

                if (particle.x - 1 >= 0 && particle.y - 1 >= 0)
                {
                    if (grid.GetPixel(particle.x - 1, particle.y - 1) == true)
                        return true;
                }

                if (particle.x - 1 >= 0 && particle.y + 1 < grid.Height)
                {
                    if (grid.GetPixel(particle.x - 1, particle.y + 1) == true)
                        return true;
                }
            }

            // right
            if (particle.x + 1 < grid.Width)
            {
                if (grid.GetPixel(particle.x + 1, particle.y) == true)
                    return true;
            }
            // left
            if (particle.x - 1 >= 0)
            {
                if (grid.GetPixel(particle.x - 1, particle.y) == true)
                    return true;
            }
            // top
            if (particle.y + 1 < grid.Height)
            {
                if (grid.GetPixel(particle.x, particle.y+1) == true)
                    return true;
            }
            // bottom
            if (particle.y - 1 >= 0)
            {
                if (grid.GetPixel(particle.x, particle.y-1) == true)
                    return true;
            }

            return false;
        }

        private void reSeed()
        {
            m_randomNumberGen = new Random();
        }

        /// <summary>
        /// Midpoint circle algorithm
        /// can be found http://en.wikipedia.org/wiki/Midpoint_circle_algorithm
        /// </summary>
        /// <param name="x0"></param>
        /// <param name="y0"></param>
        /// <param name="radius"></param>
        /// <param name="bitmap"></param>
        /// <param name="color"></param>
        /// <param name="startRegion"></param>
        private List<Particle> rasterCircle(int x0, int y0, int radius, Bitmap bitmap, Color color)
        {
            List<Particle> region = new List<Particle>();
            int f = 1 - radius;
            int ddF_x = 1;
            int ddF_y = -2 * radius;
            int x = 0;
            int y = radius;

            region.Add(new Particle(x0, y0 + radius));
            region.Add(new Particle(x0, y0 - radius));
            region.Add(new Particle(x0 + radius, y0));
            region.Add(new Particle(x0 - radius, y0));

            while (x < y)
            {
                // ddF_x == 2 * x + 1;
                // ddF_y == -2 * y;
                // f == x*x + y*y - radius*radius + 2*x - y + 1;
                if (f >= 0)
                {
                    y--;
                    ddF_y += 2;
                    f += ddF_y;
                }
                x++;
                ddF_x += 2;
                f += ddF_x;

                region.Add(new Particle(x0 + x, y0 + y));
                region.Add(new Particle(x0 - x, y0 + y));
                region.Add(new Particle(x0 + x, y0 - y));
                region.Add(new Particle(x0 - x, y0 - y));

                region.Add(new Particle(x0 + y, y0 + x));
                region.Add(new Particle(x0 - y, y0 + x));
                region.Add(new Particle(x0 + y, y0 - x));
                region.Add(new Particle(x0 - y, y0 - x));
            }

            return region;
        }
    }
}
